/*
 * Copyright (c) 2019 Tada AB and other contributors, as listed below.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the The BSD 3-Clause License
 * which accompanies this distribution, and is available at
 * http://opensource.org/licenses/BSD-3-Clause
 *
 * Contributors:
 *   Chapman Flack
 */
package org.postgresql.pljava.internal;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import java.nio.charset.Charset;
import static java.nio.charset.StandardCharsets.US_ASCII;

import java.sql.SQLException;

import static java.util.Collections.unmodifiableSet;
import java.util.EnumSet;
import java.util.Set;

import org.w3c.dom.Node;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

import org.xml.sax.ext.Attributes2;
import org.xml.sax.ext.Attributes2Impl;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.DefaultHandler2;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.ext.LexicalHandler;

/**
 * Base class implementing the tedious parts of a SAX {@code XMLReader} whose
 * XML content is synthesized on the fly.
 *<p>
 * An implementing class provides {@link #parse(InputSource)} (which might do as
 * little as ignoring its argument and calling {@code super.parse()}), and the
 * workhorse {@link #next()}, which should return an {@link EventCarrier} on
 * every call, then {@code null} when no parse events remain. An
 * {@code EventCarrier} is a closure that can disgorge one or more SAX events
 * onto SAX handlers (provided by this framework) when its
 * {@link EventCarrier#toSAX() toSAX()} method is called. Start- and
 * end-document events are synthesized here, so only the events in between
 * should be generated by {@code EventCarrier}s.
 *<p>
 * An implementing class could return a single {@code EventCarrier} that will
 * provide all of the XML content, or a sequence of {@code EventCarrier}s each
 * supplying one event or more, at the discretion of the implementor; if the
 * content might be large or complex to generate, breaking it into multiple
 * {@code EventCarrier}s can provide a StAX-like ability to pull it in smaller
 * pieces.
 *<p>
 * This odd hybrid based on SAX is used, rather than simply basing a synthetic
 * XML source directly on StAX, because of the numerous bugs in the Java
 * runtime's implementation of StAX-to-TRAX bridging. Those are not so much in
 * StAX itself (a tidy API), nor in the TRAX transformer implementations, but in
 * the JRE classes added to bridge the two when StAX was added. The worst are in
 * the handling of XML 'content' fragments, which are explicitly permitted by
 * SQL/XML. A look at the bridge classes' code does not show a complete lack of
 * attention to that case, but the code is broken and, after so many years,
 * will probably not have its behavior changed. Because the
 * {@code java.sql.SQLXML} API is expressly designed to allow easily obtaining a
 * default {@code Source} to pass to a TRAX transformation, and to handle
 * SQL/XML content that can be fragmentary, it follows that the default flavor
 * of {@code Source} to return (and to implement synthetically here) must not be
 * StAX. SAX is well supported and plays well with TRAX, even for content
 * fragments.
 */
public abstract class SyntheticXMLReader implements XMLReader
{
	/**
	 * A final, pre-allocated, read-only {@code Attributes2} that is empty, for
	 * convenience in {@code startElement} calls for elements with no
	 * attributes.
	 */
	public static final Attributes2 NO_ATTRIBUTES = new EmptyAttributes2();

	/**
	 * A per-instance, pre-allocated {@code FluentAttributes2} that can be
	 * re-used (but not across threads) for convenience in {@code startElement}
	 * calls for elements with attributes.
	 */
	public final FluentAttributes2 m_attributes = new FluentAttributes2();

	public enum SAX2FEATURE
	{
		EXTERNAL_GENERAL_ENTITIES("external-general-entities", null),
		EXTERNAL_PARAMETER_ENTITIES("external-parameter-entities", null),
		IS_STANDALONE("is-standalone"),
		LEXICAL_HANDLER_PARAMETER_ENTITIES("lexical-handler/parameter-entities",
		                                   null),
		NAMESPACES("namespaces", true),
		NAMESPACE_PREFIXES("namespace-prefixes", true),
		RESOLVE_DTD_URIS("resolve-dtd-uris", true),
		STRING_INTERNING("string-interning", true),
		UNICODE_NORMALIZATION_CHECKING("unicode-normalization-checking", true),
		USE_ATTRIBUTES2("use-attributes2", false),
		USE_LOCATOR2("use-locator2", false),
		USE_ENTITY_RESOLVER2("use-entity-resolver2", true),
		VALIDATION("validation", true),
		XMLNS_URIS("xmlns-uris", true),
		XML_1_1("xml-1.1", false);

		private static final String PREFIX = "http://xml.org/sax/features/";

		static final Set<SAX2FEATURE> STANDARD_DEFAULTS;
		static final Set<SAX2FEATURE> STANDARD_INITIALIZED;

		public final String  featureId;
		public final boolean writable;
		public final Boolean standardDefault; // null if unspecified in standard

		public String featureUri()
		{
			return PREFIX + featureId;
		}

		/**
		 * @return null if not recognized
		 */
		public static SAX2FEATURE fromUri(String uri)
		{
			if ( null != uri  &&  uri.startsWith(PREFIX) )
			{
				String s = uri.substring(PREFIX.length());
				for ( SAX2FEATURE f : values() )
					if ( f.featureId.equals(s) )
						return f;
			}
			return null;
		}

		SAX2FEATURE(String id)
		{
			featureId = id;
			writable = false;
			standardDefault = null;
		}

		SAX2FEATURE(String id, Boolean standardDefault)
		{
			featureId = id;
			writable = true;
			this.standardDefault = standardDefault;
		}

		static
		{
			EnumSet<SAX2FEATURE> dflts = EnumSet.noneOf(SAX2FEATURE.class);
			EnumSet<SAX2FEATURE> inits = dflts.clone();

			for ( SAX2FEATURE f : values() )
			{
				if ( null == f.standardDefault )
					continue;
				if ( f.standardDefault.booleanValue() )
					dflts.add(f);
				inits.add(f);
			}

			STANDARD_DEFAULTS = unmodifiableSet(dflts);
			STANDARD_INITIALIZED = unmodifiableSet(inits);
		}
	}

	public enum ApacheFeature
	{
		DISALLOW_DOCTYPE_DECL("disallow-doctype-decl", false),
		XINCLUDE("xinclude", false),
		LOAD_EXTERNAL_DTD("nonvalidating/load-external-dtd", true);

		private static final String PREFIX = "http://apache.org/xml/features/";

		static final Set<ApacheFeature> STANDARD_DEFAULTS;

		public final String  featureId;
		public final Boolean standardDefault;

		public String featureUri()
		{
			return PREFIX + featureId;
		}

		/**
		 * @return null if not recognized
		 */
		public static ApacheFeature fromUri(String uri)
		{
			if ( null != uri  &&  uri.startsWith(PREFIX) )
			{
				String s = uri.substring(PREFIX.length());
				for ( ApacheFeature f : values() )
					if ( f.featureId.equals(s) )
						return f;
			}
			return null;
		}

		ApacheFeature(String id, Boolean standardDefault)
		{
			featureId = id;
			this.standardDefault = standardDefault;
		}

		static
		{
			EnumSet<ApacheFeature> dflts = EnumSet.noneOf(ApacheFeature.class);

			for ( ApacheFeature f : values() )
			{
				if ( f.standardDefault.booleanValue() )
					dflts.add(f);
			}

			STANDARD_DEFAULTS = unmodifiableSet(dflts);
		}
	}

	public enum SAX2PROPERTY
	{
		DECLARATION_HANDLER("declaration-handler", DeclHandler.class),
		DOCUMENT_XML_VERSION("document-xml-version", String.class, false),
		DOM_NODE("dom-node", Node.class),
		LEXICAL_HANDLER("lexical-handler", LexicalHandler.class),
		XML_STRING("xml-string", String.class, false);

		private static final String PREFIX = "http://xml.org/sax/properties/";

		public final String  propertyId;
		public final boolean writable;
		public final Class<?> requiredClass;

		public String propertyUri()
		{
			return PREFIX + propertyId;
		}

		public static SAX2PROPERTY fromUri(String uri)
		throws SAXNotRecognizedException
		{
			if ( null != uri  &&  uri.startsWith(PREFIX) )
			{
				String s = uri.substring(PREFIX.length());
				for ( SAX2PROPERTY p : values() )
					if ( p.propertyId.equals(s) )
						return p;
			}
			throw new SAXNotRecognizedException(uri);
		}

		public boolean valueOk(Object v)
		{
			return null == v  ||  requiredClass.isInstance(v);
		}

		SAX2PROPERTY(String id, Class<?> reqClass)
		{
			propertyId = id;
			requiredClass = reqClass;
			this.writable = true;
		}

		SAX2PROPERTY(String id, Class<?> reqClass, boolean writable)
		{
			propertyId = id;
			requiredClass = reqClass;
			this.writable = writable;
		}
	}
	
	private Set<SAX2FEATURE> m_featuresSet =
		EnumSet.copyOf(SAX2FEATURE.STANDARD_DEFAULTS);
	
	private Set<SAX2FEATURE> m_featuresKnown =
		EnumSet.copyOf(SAX2FEATURE.STANDARD_INITIALIZED);

	private Set<ApacheFeature> m_apacheFeaturesSet =
		EnumSet.copyOf(ApacheFeature.STANDARD_DEFAULTS);

	private final Object[] m_propertyValue =
		new Object[SAX2PROPERTY.values().length];

	private final DefaultHandler2 m_dummy = new DefaultHandler2();
	
	private ContentHandler m_contentHandler;
	private DTDHandler m_dtdHandler;
	private EntityResolver m_entityResolver;
	private ErrorHandler m_errorHandler;

	/*
	 * The following (with names ending in _) are versions of the above meant
	 * for quick reference: whenever they are set, if the value is null, a dummy
	 * that supports the methods with no-ops will be substituted; if an
	 * EntityResolver (not an EntityResolver2) is supplied, it will be wrapped
	 * to implement the missing methods with no-ops.
	 */
	private ContentHandler m_contentHandler_;
	private DTDHandler m_dtdHandler_;
	private EntityResolver2 m_entityResolver_;
	private ErrorHandler m_errorHandler_;
	private DeclHandler m_declHandler_;
	private LexicalHandler m_lexicalHandler_;
	
	@Override
	public boolean getFeature(String uri)
	throws SAXNotRecognizedException, SAXNotSupportedException
	{
		SAX2FEATURE f = SAX2FEATURE.fromUri(uri);
		if ( m_featuresKnown.contains(f) )
			return m_featuresSet.contains(f);
		throw new SAXNotSupportedException(uri);
	}
	
	@Override
	public void setFeature(String uri, boolean value)
	throws SAXNotRecognizedException, SAXNotSupportedException
	{
		SAX2FEATURE f = SAX2FEATURE.fromUri(uri);
		if ( null != f )
		{
			if ( ! f.writable )
				throw new SAXNotSupportedException(uri);
			m_featuresKnown.add(f);
			if ( value )
				m_featuresSet.add(f);
			else
				m_featuresSet.remove(f);
			return;
		}
		ApacheFeature af = ApacheFeature.fromUri(uri);
		if ( null != af )
		{
			if ( value )
				m_apacheFeaturesSet.add(af);
			else
				m_apacheFeaturesSet.remove(af);
			return;
		}
		throw new SAXNotRecognizedException(uri);
	}
	
	@Override
	public Object getProperty(String uri)
	throws SAXNotRecognizedException, SAXNotSupportedException
	{
		SAX2PROPERTY p = SAX2PROPERTY.fromUri(uri);
		return m_propertyValue[p.ordinal()];
		// XXX make some provision for unsupported settings
	}
	
	@Override
	public void setProperty(String uri, Object value)
	throws SAXNotRecognizedException, SAXNotSupportedException
	{
		SAX2PROPERTY p = SAX2PROPERTY.fromUri(uri);
		if ( ! p.writable  ||  ! p.valueOk(value) )
			throw new SAXNotSupportedException(uri);
		m_propertyValue[p.ordinal()] = value;
		switch ( p )
		{
		case DECLARATION_HANDLER:
			m_declHandler_ = null == value ? m_dummy : (DeclHandler)value;
			break;
		case LEXICAL_HANDLER:
			m_lexicalHandler_ = null == value ? m_dummy : (LexicalHandler)value;
			break;
		default:
		}
	}
	
	@Override
	public void setEntityResolver(EntityResolver resolver)
	{
		m_entityResolver = resolver;
		// XXX this should also be sensitive to USE_ENTITY_RESOLVER2
		if ( null == resolver )
			m_entityResolver_ = m_dummy;
		else if ( resolver instanceof EntityResolver2 )
			m_entityResolver_ = (EntityResolver2)resolver;
		else
			m_entityResolver_ = new EntityResolverWrapper(resolver);
	}
	
	@Override
	public EntityResolver getEntityResolver()
	{
		return m_entityResolver;
	}
	
	@Override
	public void setDTDHandler(DTDHandler handler)
	{
		m_dtdHandler = handler;
		m_dtdHandler_ = null != handler ? handler : m_dummy;
	}
	
	@Override
	public DTDHandler getDTDHandler()
	{
		return m_dtdHandler;
	}
	
	@Override
	public void setContentHandler(ContentHandler handler)
	{
		m_contentHandler = handler;
		m_contentHandler_ = null != handler ? handler : m_dummy;
	}
	
	@Override
	public ContentHandler getContentHandler()
	{
		return m_contentHandler;
	}
	
	@Override
	public void setErrorHandler(ErrorHandler handler)
	{
		m_errorHandler = handler;
		m_errorHandler_ = null != handler ? handler : m_dummy;
	}

	@Override
	public ErrorHandler getErrorHandler()
	{
		return m_errorHandler;
	}

	/**
	 * The workhorse method for an implementing class to supply.
	 *<p>
	 * It should return {@code null} when no more events are available, and
	 * until then, on each call should return an {@link EventCarrier} subclass
	 * whose {@link EventCarrier#toSAX() toSAX()} method will disgorge one or
	 * more SAX events.
	 * @return An EventCarrier, or null when no more events are to be returned.
	 */
	protected abstract EventCarrier next();
	
	/**
	 * The only {@code parse} variant that the implementing class needs to
	 * supply.
	 *<p>
	 * An implementation could do as little as ignoring its {@code InputSource}
	 * argument and calling the zero-argument {@code super.parse()}.
	 */
	@Override
	public abstract void parse(InputSource input)
	throws IOException, SAXException;

	/**
	 * If not overridden, calls {@link #parse(InputSource)} with the system-id
	 * wrapped in an {@code InputSource}.
	 */
	@Override
	public void parse (String systemId) throws IOException, SAXException
	{
		parse(new InputSource(systemId));
	}

	/**
	 * Where the work happens.
	 *<p>
	 * Synthesizes a {@code startDocument}, then loops calling {@code next()}
	 * until it returns null, calling {@code toSAX} on every returned
	 * {@code EventCarrier}, and finally synthesizes an {@code endDocument}.
	 */
	protected final void parse() throws IOException, SAXException
	{
		m_contentHandler_.startDocument();

		EventCarrier c;

		try
		{
			while ( null != ( c = next() ) )
				c.toSAX();
		}
		catch ( SQLException e )
		{
			throw new IOException(e.getMessage(), e);
		}

		m_contentHandler_.endDocument();
	}

	/**
	 * Produce an {@code EventCarrier} that wraps a checked exception and will
	 * rethrow it when used, which can be returned by the {@code next()} method,
	 * which is not declared to throw any checked exceptions itself.
	 *<p>
	 * To simplify callers, the exception parameter is allowed to be a
	 * {@code RuntimeException}, in which case it will simply be rethrown here
	 * rather than wrapped.
	 * @param e An Exception, which may be a RuntimeException or checked.
	 * @return An EventCarrier wrapping the exception, if it is checked.
	 */
	protected EventCarrier exceptionCarrier(final Exception e)
	{
		if ( e instanceof RuntimeException )
			throw (RuntimeException)e;
		return new ExceptionCarrier(e);
	}
	
	/**
	 * Obtain a {@code Reader} given a system-id and a character set.
	 *<p>
	 * If not overridden, this method delegates to
	 * {@link #sysIdToInputStream} and wraps the result in a {@code Reader} for
	 * the specified character set.
	 */
	protected Reader sysIdToReader(URI sysId, Charset cs)
	throws IOException, SAXException
	{
		InputStream is = sysIdToInputStream(sysId);
		if ( null == is )
			return null;
		return new InputStreamReader(is, cs.newDecoder());
	}
	
	/**
	 * Obtain an {@code InputStream} given a system-id.
	 *<p>
	 * If not overridden, this method tries {@code toURL().openStream()} on the
	 * supplied system-id, wrapping exceptions as needed to throw only those
	 * appropriate in a SAX method.
	 */
	protected InputStream sysIdToInputStream(URI sysId)
	throws IOException, SAXException
	{
		try {
			return sysId.toURL().openStream();
		}
		catch ( MalformedURLException mue ) {
			throw (SAXNotSupportedException)
				new SAXNotSupportedException(sysId.toString()).initCause(mue);
		}
	}
	
	/**
	 * Obtain a {@code Reader} given an {@code InputSource}.
	 *<p>
	 * If not overridden, this method returns the {@code Reader} directly
	 * contained in the source if there is one, or one that wraps the source's
	 * byte stream and character encoding if available, or the result of
	 * {@link #sysIdToReader sysIdToReader} on the source's system-id
	 * if available.
	 */
	protected Reader sourceToReader(InputSource input)
	throws IOException, SAXException
	{
		Reader r = input.getCharacterStream();
		if ( null != r )
			return r;

		String encoding = input.getEncoding();
		Charset cs = (null != encoding) ? Charset.forName(encoding) : US_ASCII;

		InputStream is = input.getByteStream();
		if ( null != is )
			return new InputStreamReader(is, cs.newDecoder());

		String sysId = input.getSystemId();
		if ( null == sysId )
			throw new SAXNotSupportedException(input.toString());

		URI uri;
		try {
			uri = new URI(sysId);
		}
		catch ( URISyntaxException use ) {
			throw (SAXNotSupportedException)
				new SAXNotSupportedException(input.toString()).initCause(use);
		}

		if ( ! uri.isAbsolute() )
			throw new IllegalArgumentException(uri.toString());

		r = sysIdToReader(uri, cs);
		if ( null != r )
			return r;
		
		throw new SAXNotSupportedException(input.toString());
	}

	/**
	 * Wrapper for an {@code EntityResolver} allowing it to be used as an
	 * {@code EntityResolver2}.
	 */
	static class EntityResolverWrapper extends DefaultHandler2
	{
		private final EntityResolver m_entityResolver;

		EntityResolverWrapper(EntityResolver er)
		{
			m_entityResolver = er;
		}

		@Override
		public InputSource resolveEntity(
			String name, String publicId,
			String baseURI, String systemId)
		throws SAXException, IOException
		{
			return resolveEntity(publicId, systemId);
		}

		@Override
		public InputSource resolveEntity(String publicId, String systemId)
		throws SAXException, IOException
		{
			return m_entityResolver.resolveEntity(publicId, systemId);
		}
	}

	/**
	 * Base class for a closure carrying one or more SAX events.
	 *<p>
	 * Only {@link #toSAX} needs to be provided by an implementing class.
	 * It can use {@link #content}, {@link #dtd}, {@link #entity}, {@link #err},
	 * {@link #decl}, and {@link #lex} to obtain the various SAX handlers onto
	 * which it should disgorge events. Those methods never return null; a no-op
	 * handler will be returned if the consumer code did not register a handler
	 * of the corresponding type.
	 *<p>
	 * Additional convenience methods are provided for generating the most
	 * common SAX parse events.
	 */
	public abstract class EventCarrier
	{
		protected ContentHandler content()
		{
			return m_contentHandler_;
		}

		protected DTDHandler dtd()
		{
			return m_dtdHandler_;
		}

		protected EntityResolver2 entity()
		{
			return m_entityResolver_;
		}

		protected ErrorHandler err()
		{
			return m_errorHandler_;
		}

		protected DeclHandler decl()
		{
			return m_declHandler_;
		}

		protected LexicalHandler lex()
		{
			return m_lexicalHandler_;
		}

		/**
		 * Return the per-instance, reusable
		 * {@link FluentAttributes2 FluentAttributes2} instance, without
		 * clearing it first, so the attributes from its last use can be
		 * reused or modified.
		 */
		protected FluentAttributes2 attrs()
		{
			return m_attributes;
		}

		/**
		 * Return the per-instance, reusable
		 * {@link FluentAttributes2 FluentAttributes2} instance,
		 * clearing it first.
		 */
		protected FluentAttributes2 cleared()
		{
			return m_attributes.cleared();
		}

		/**
		 * Write a {@code String} value as character content.
		 */
		protected void characters(String s) throws SAXException
		{
			m_contentHandler_.characters(s.toCharArray(), 0, s.length());
		}

		/**
		 * Write a {@code String} value as a {@code CDATA} segment.
		 */
		protected void cdataCharacters(String s) throws SAXException
		{
			m_lexicalHandler_.startCDATA();
			try
			{
				m_contentHandler_.characters(s.toCharArray(), 0, s.length());
			}
			finally
			{
				m_lexicalHandler_.endCDATA();
			}
		}

		/**
		 * Start an element with only a local name and no attributes.
		 */
		protected void startElement(String localName) throws SAXException
		{
			m_contentHandler_.startElement(
				"", localName, localName, NO_ATTRIBUTES);
		}

		/**
		 * Start an element with only a local name, and attributes.
		 */
		protected void startElement(String localName, Attributes atts)
		throws SAXException
		{
			m_contentHandler_.startElement("", localName, localName, atts);
		}

		/**
		 * End an element with only a local name.
		 */
		protected void endElement(String localName) throws SAXException
		{
			m_contentHandler_.endElement("", localName, localName);
		}

		public abstract void toSAX()
		throws IOException, SAXException, SQLException;
	}

	/**
	 * An {@code EventCarrier} that only wraps an exception, which will be
	 * rethrown when {@code toSAX()} is called, wrapped in a
	 * {@code SAXException} if it is not a {@code SAXException} or
	 * {@code IOException}.
	 */
	class ExceptionCarrier extends EventCarrier
	{
		private Exception e;
		ExceptionCarrier(Exception e)
		{
			this.e = e;
		}

		@Override
		public void toSAX()
		throws IOException, SAXException, SQLException
		{
			if ( e instanceof IOException )
				throw (IOException)e;
			if ( e instanceof SAXException )
				throw (SAXException)e;
			if ( e instanceof SQLException )
				throw (SQLException)e;
			throw new SQLException(e.getMessage(), e);
		}
	}

	public static class EmptyAttributes2 extends Attributes2Impl
	{
		@Override
		public void addAttribute(
			String uri, String localName, String qName,
			String type, String value)
		{
			throw new UnsupportedOperationException(
				"addAttribute() to the NO_ATTRIBUTES instance");
		}

		@Override
		public void setAttributes(Attributes atts)
		{
			throw new UnsupportedOperationException(
				"setAttributes() to the NO_ATTRIBUTES instance");
		}
	}

	/**
	 * Subclass of {@link Attributes2Impl} that also provides chainable methods
	 * so attribute information can be supplied in a fluent style.
	 */
	public static class FluentAttributes2 extends Attributes2Impl
	{
		public FluentAttributes2 cleared()
		{
			clear();
			return this;
		}

		public FluentAttributes2 withAttribute(String localName)
		{
			addAttribute("", localName, localName, "CDATA", "");
			return this;
		}

		public FluentAttributes2 withAttribute(String localName, String value)
		{
			addAttribute("", localName, localName, "CDATA", value);
			return this;
		}

		public FluentAttributes2 withAttribute(String uri, String localName,
			String qName, String type, String value)
		{
			addAttribute(uri, localName, qName, type, value);
			return this;
		}

		public FluentAttributes2 withoutAttribute(int index)
		{
			removeAttribute(index);
			return this;
		}

		public FluentAttributes2 withAttribute(int index, String uri,
			String localName, String qName, String type, String value)
		{
			setAttribute(index, uri, localName, qName, type, value);
			return this;
		}

		public FluentAttributes2 withAttributes(Attributes atts)
		{
			setAttributes(atts);
			return this;
		}

		public FluentAttributes2 withLocalName(int index, String localName)
		{
			setLocalName(index, localName);
			return this;
		}

		public FluentAttributes2 withQName(int index, String qName)
		{
			setQName(index, qName);
			return this;
		}

		public FluentAttributes2 withType(int index, String type)
		{
			setType(index, type);
			return this;
		}

		public FluentAttributes2 withURI(int index, String uri)
		{
			setURI(index, uri);
			return this;
		}

		public FluentAttributes2 withValue(int index, String value)
		{
			setValue(index, value);
			return this;
		}

		public FluentAttributes2 withDeclared(int index, boolean value)
		{
			setDeclared(index, value);
			return this;
		}

		public FluentAttributes2 withSpecified(int index, boolean value)
		{
			setSpecified(index, value);
			return this;
		}
	}
}
